package br.unidocs.servico;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;

import br.unidocs.modelo.entidade.Arquivo;
import br.unidocs.modelo.entidade.Tag;
import br.unidocs.modelo.entidade.Usuario;

/**
 * Session Bean implementation class UsuarioServicesImpl
 */
@Stateless
// nao mantem estado entre chamadas; o metodo processa e te devolve algo. as
// informacoes gravadas dentro da classe serao perdidas;
// Outras annotations - Statefull, que mantem na memoria.
// e o terceiro é Nested tipo de servicos
// - consumir mensagens;
public class ArquivoEJBImpl implements ArquivoEJB {
	private static Logger log = Logger.getLogger(ArquivoEJBImpl.class);

	private static final String DIRETORIO_ARQUIVOS = "C:\\Temp\\";
	private static final String EXTENSAO_PADRAO = ".bin";

	@PersistenceContext(unitName = "UnidocsPU")
	EntityManager em; // inversao de controle , note a annotation. assim o
						// servidor injeta gerenciador de entidades

	/**
	 * Default constructor.
	 */
	public ArquivoEJBImpl() {
		//
	}

	@Override
	// medodo criado porque estamos sobrescrevendo o metodo da interfdace
	public Arquivo localizaArquivo(int id) {
		Arquivo arq = em.find(Arquivo.class, id);
		if (arq != null) {
			em.refresh(arq);// para nao pegar da memoria, forcar buscar no
							// banco.
			return arq;
		}
		return null;
	}

	@Override
	public Arquivo localizaArquivoParaDetalhamento(int id) {
		Arquivo arq = em.find(Arquivo.class, id);
		if (arq != null) {
			em.refresh(arq);// para nao pegar da memoria, forcar buscar no
							// banco.
			arq.getComentarios().size();
			arq.getTags().size();
			return arq;
		}
		return null;
	}

	@Override
	public Integer criaArquivo(String nomeArquivo, String descricao,
			String[] tags, Usuario usuario, byte[] binario) {
		Arquivo arquivo = new Arquivo();
		arquivo.setDataUpload(Calendar.getInstance().getTime());
		arquivo.setDescricao(descricao);
		arquivo.setNome(nomeArquivo);
		arquivo.setUsuario(usuario);
		arquivo.setTamanho(binario.length);

		arquivo.setTags(new ArrayList<Tag>());

		for (String stringTag : tags) {
			Tag tag;
			try {
				tag = (Tag) em
						.createQuery(
						// nao eh sql, eh jpql... similar a sql. aqui é tudo
						// referenciando atributos nas classes, nao os nomes no
						// banco;
								"select t from Tag t where UPPER(t.nome) = UPPER(:nome)")
						.setParameter("nome", stringTag.trim())
						.getSingleResult();
			} catch (NonUniqueResultException e) {
				throw new RuntimeException(
						"Duas tags com o mesmo nome encontrado!!!!", e);
			} catch (NoResultException e) {
				tag = new Tag();
				tag.setNome(stringTag.trim());
			}
			arquivo.getTags().add(tag);
		}

		em.persist(arquivo); // o jpa se vira pra localizar campos etc
		File arqDestino = new File(DIRETORIO_ARQUIVOS + arquivo.getId()
				+ EXTENSAO_PADRAO);
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(arqDestino, false);
			fos.write(binario);
		} catch (FileNotFoundException e) {
			throw new RuntimeException("Não foi possível localizar o arquivo.",
					e);
		} catch (IOException e) {
			throw new RuntimeException("Erro ao gravar o arquivo no disco.", e);
		} finally {
			try {
				if (fos != null) {
					fos.close();
				}
			} catch (IOException e) {
				throw new RuntimeException(
						"Erro ao fechar o arquivo. Verifique se o mesmo está \"aberto\".",
						e);
			}
		}
		return arquivo.getId();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Arquivo> listaArquivos() {
		return em.createQuery("select a from Arquivo a order by a.id asc")
				.getResultList(); // a é idem * aqui
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Arquivo> pesquisaPorTags(String[] tags) {
		// constroi a pesquisa
		String where = "";

		for (int i = 0; i < tags.length; i++) {
			where += " upper(t.nome) like upper(:param" + i + ")";
			if (i < tags.length - 1) {
				where += " or ";
			}
		}
		Query q = em
				.createQuery("select distinct a from Arquivo a join a.tags t where "
						+ where + " order by a.id asc");
		for (int i = 0; i < tags.length; i++) {
			q.setParameter("param" + i, "%" + tags[i] + "%");
		}
		return q.getResultList();
		// upper(t.nome) = upper(:nome)")
		// /* .setHint(QueryHints.REFRESH, "true") */
		// .setParameter("nome", tag).getResultList();
	}

	@Override
	public Arquivo atualizaArquivo(Arquivo a, List<Tag> tagsAAdicionar,
			List<Tag> tagsARemover) {
		log.info("Atualizando o arquivo " + a.getId() + " - " + a.getNome());
		a = em.merge(a);
		if (tagsARemover != null) {
			for (Tag tag : tagsARemover) {
				log.debug("Removendo a tag " + tag.getNome());
				Tag managedTag = a.getTags().get(a.getTags().indexOf(tag));
				a.getTags().remove(managedTag);
				managedTag.getArquivos().remove(a);
			}
		}
		if (tagsAAdicionar != null) {
			for (Tag novaTag : tagsAAdicionar) {
				log.debug("Adicionando a tag " + novaTag.getNome());
				try {
					novaTag = (Tag) em
							.createQuery(
							// nao eh sql, eh jpql... similar a sql. aqui é tudo
							// referenciando atributos nas classes, nao os nomes
							// no
							// banco;
									"select t from Tag t where UPPER(t.nome) = UPPER(:nome)")
							.setParameter("nome", novaTag.getNome().trim())
							.getSingleResult();
				} catch (NonUniqueResultException e) {
					throw new RuntimeException(
							"Duas tags com o mesmo nome encontrado!!!!", e);
				} catch (NoResultException e) {
					// Utiliza a propria tag
				}
				a.getTags().add(novaTag);
			}
		}
		return a;
	}

	@Override
	public void excluiArquivo(Arquivo a) {
		// 1 - apaga registro no banco de dados
		// tornando a entidade gerenciada pelo entity manager novamente após uma
		// possível serialização
		if (!em.contains(a)) {
			a = em.find(Arquivo.class, a.getId());
		}

		em.remove(a);

		// 2 - apaga registro no HD
		File f = new File(DIRETORIO_ARQUIVOS + a.getId() + EXTENSAO_PADRAO);
		if (!f.exists()) {
			return;
		}

		try {
			if (!f.delete()) {
				System.out.println("ERRO: Não foi possível apagar o arquivo "
						+ f.getName());
			}
		} catch (SecurityException e) {
			System.out.println("ERRO: Sem permissão para apagar o arquivo "
					+ f.getName());
		}

	}

}
